package charts.series.pies ;

import charts.series.Element;
import flash.events.Event;
import caurina.transitions.Tweener;
import caurina.transitions.Equations;
import flash.geom.Point;
import flash.events.MouseEvent;


typedef PIELABELSTRUCT = {
	var label		: String;
	var color		: Float;
	var font_size		: Float;
	var onclick		: Dynamic;
};

typedef ANIMATESTRUCT = {
	var type		: String;
	var distance		: Int;
};

typedef OFFSETSTRUCT = {
	var top			: Float;
	var bottom		: Float;
	var left		: Float;
	var right		: Float;
};

class PieSliceContainer extends Element {
	
		static var TO_RADIANS: Float = Math.PI / 180;
	
		var animating:Bool;
		var pieSlice:PieSlice;
		var pieLabel:PieLabel;
		var pieRadius: Float;

		var tick_size: Float;
		var tick_extension_size: Float;
		var label_margin: Float;
		var animationOffset: Float;
	
		var saveX: Float;
		var saveY: Float;
		var moveToX: Float;
		var moveToY: Float;
	
		var original_alpha: Float;

	//
	// this holds the slice and the text.
	// we want to rotate the slice, but not the text, so
	// this container holds both
	//
	public function new ( index: Int, value:Properties )
	{
		super();

		tick_size = 10;
		tick_extension_size  = 4;
		label_margin = 2;
		animationOffset = 30;

		//
		// replace magic in the label:
		//
		// value.set('label', this.replace_magic_values( value.get('label') ) );
		
		
		//tr.aces( 'pie', value.get('animate') );
		
		this.pieSlice = new PieSlice( index, value );
		this.addChild( this.pieSlice );
		var textlabel:String = value.get('label');
		
		//
		// we set the alpha of the parent container
		//
		this.alpha = this.original_alpha = value.get('alpha');
		//
		if ( !value.has('label-color') )
			value.set('label-color', value.get('color'));
		
		var l:String = if (value.get('onclickables')) '' else value.get('label');
		
		var pieLabelCtor : PIELABELSTRUCT =  {
				label:			l,
				color:			value.get('label-color'),
				font_size:		value.get('font-size'),
				onclick:		value.get('onclick') };

		this.pieLabel = new PieLabel(pieLabelCtor);
		this.addChild( this.pieLabel );
		
		this.attach_events__(value);
		this.animating = false;
	}
	
	public function is_over():Bool {
		return this.pieSlice.is_over;
	}
	
	public function get_slice():Element {
		return this.pieSlice;
	}
	
	public function get_label():PieLabel {
		return this.pieLabel;
	}
	
	
	//
	// the axis makes no sense here, let's override with null and write our own.
	//
	public override function resize ( sc:ScreenCoordsBase ): Void {}
	
	public function is_label_on_screen( sc:ScreenCoordsBase, slice_radius: Float ): Bool {
		
		return this.pieLabel.move_label(
			slice_radius + 10,
			sc.get_center_x(),
			sc.get_center_y(),
			this.pieSlice.angle+(this.pieSlice.slice_angle/2) );
	}
	
	public function pie_resize( sc:ScreenCoordsBase, slice_radius: Float ): Void {
		
		this.pieRadius = slice_radius;  // save off value for later use
		this.pieSlice.pie_resize(sc, slice_radius);

		var ticAngle: Float = this.getTicAngle();

		this.saveX = this.x;
		this.saveY = this.y;
		this.moveToX = this.x + (animationOffset * Math.cos(ticAngle * TO_RADIANS));
		this.moveToY = this.y + (animationOffset * Math.sin(ticAngle * TO_RADIANS));

		if (this.pieLabel.visible)
		{
			var lblRadius: Float = slice_radius + this.tick_size;
			var lblAngle: Float = ticAngle * TO_RADIANS;

			this.pieLabel.x = this.pieSlice.x + lblRadius * Math.cos(lblAngle);
			this.pieLabel.y = this.pieSlice.y + lblRadius * Math.sin(lblAngle);

			if (this.isRightSide())
			{
				this.pieLabel.x += this.tick_extension_size + this.label_margin;
			}
			else
			{
				//if legend stands to the left side of the pie
				this.pieLabel.x =
					this.pieLabel.x -
					this.pieLabel.width -
					this.tick_extension_size -
					this.label_margin -
					4;
			}
			this.pieLabel.y -= this.pieLabel.height / 2;

			this.drawTicLines();
		}
	}
	
	public override function get_tooltip():String {
		return this.pieSlice.get_tooltip();
	}
	
	public override function get_tip_pos(): {x:Float, y:Float} {
		var p:flash.geom.Point = this.localToGlobal( new flash.geom.Point(this.mouseX, this.mouseY) );
		return {x:p.x,y:p.y};
	}
	
	//
	// override this. I think this needs to be moved Into an
	// animation manager?
	//
	// BTW this is called attach_events__ because Element has an
	//     attach_events already. I guess we need to fix one of them
	//
	function attach_events__(value:Properties):Void {
		
		//
		// TODO: either move this Into properties
		//       props.as(Array).get('moo');
		//       or get rid of type checking
		//
		
		var animate:Array<ANIMATESTRUCT> = value.get('animate');
		if (!Std.is(animate,Array)) {
			if (animate == null)
				animate = [{type:"bounce",distance:5}];
			else
				animate = new Array<ANIMATESTRUCT>();
		}
		
		//var anims:Array<TODO> = cast(animate,Array);
		//
		// end to do
		//
		
		this.addEventListener(MouseEvent.MOUSE_OVER, this.mouseOver_first, false, 0, true);
		this.addEventListener(MouseEvent.MOUSE_OUT, this.mouseOut_first, false, 0, true);
					
		for ( a in animate ) {
			switch( a.type ) {
				
				case "bounce":
					// weak references so the garbage collector will kill them:
					this.addEventListener(MouseEvent.MOUSE_OVER, this.mouseOver_bounce_out, false, 0, true);
					this.addEventListener(MouseEvent.MOUSE_OUT, this.mouseOut_bounce_out, false, 0, true);
					this.animationOffset = a.distance;
					/*break*/
					
				default:
					// weak references so the garbage collector will kill them:
					this.addEventListener(MouseEvent.MOUSE_OVER, this.mouseOver_alpha, false, 0, true);
					this.addEventListener(MouseEvent.MOUSE_OUT, this.mouseOut_alpha, false, 0, true);
					/*break*/
			}
		}
	}
	
	//
	// stop multiple tweens from running
	//
	public function mouseOver_first(event:Event):Void {
		
		if ( this.animating ) return;
		
		this.animating = true;
		Tweener.removeTweens(this);
	}
	
	public function mouseOut_first(event:Event):Void {
		Tweener.removeTweens(this);
		this.animating = false;
	}
	
	public function mouseOver_bounce_out(event:Event):Void {
		Tweener.addTween(this, {x:this.moveToX, y:this.moveToY, time:0.4, transition:"easeOutBounce"} );
	}
	
	public function mouseOut_bounce_out(event:Event):Void {
		Tweener.addTween(this, {x:this.saveX, y:this.saveY, time:0.4, transition:"easeOutBounce"} );
	}
	
	public function mouseOver_alpha(event:Event):Void {
		Tweener.addTween(this, { alpha:1, time:0.6, transition:Equations.easeOutCirc } );
	}

	public function mouseOut_alpha(event:Event):Void {
		Tweener.addTween(this, { alpha:this.original_alpha, time:0.8, transition:Equations.easeOutElastic } );
	}

	public function getLabelTopY (): Float
	{
		return this.pieLabel.y;
	}

	public function getLabelBottomY (): Float
	{
		return this.pieLabel.y + this.pieLabel.height;
	}
	
	// Y value is from 0 to sc.Height from top to bottom
	public function moveLabelDown ( sc:ScreenCoordsBase, minY: Float ): Float
	{
		if (this.pieLabel.visible)
		{
			var bAdjustToBottom:Bool = false;
			var lblTop: Float = this.getLabelTopY();
			
			if (lblTop < minY)
			{
				// adjustment is positive
				var adjust: Float = minY - lblTop;
				if ((this.pieLabel.height + minY) > (sc.bottom - 1))
				{
					// calc adjust so label bottom is at bottom of screen
					adjust = sc.bottom - this.pieLabel.height - lblTop;
					bAdjustToBottom = true;
				}
				// Adjust the Y value
				this.pieLabel.y += adjust;

				if (!bAdjustToBottom)
				{
					var lblRadius: Float = this.pieRadius + this.tick_size;
					var calcSin: Float = ((this.pieLabel.y + this.pieLabel.height / 2) - this.pieSlice.y) / lblRadius;
					calcSin = Math.max( -1, Math.min(1, calcSin));
					var Angle: Float = Math.asin(calcSin) / TO_RADIANS;

					if ((this.getTicAngle() > 90) && (this.getTicAngle() < 270))
					{
						Angle = 180 - Angle;
					}
					else if (this.getTicAngle() >= 270) 
					{
						Angle = 360 + Angle;
					}
					
					var X: Float = this.pieSlice.x + lblRadius * Math.cos(Angle * TO_RADIANS);
					if (this.isRightSide())
					{
						this.pieLabel.x = X + this.tick_extension_size + this.label_margin;
					}
					else
					{
						//if legend stands to the left side of the pie
						this.pieLabel.x = X - this.pieLabel.width -
											this.tick_extension_size - this.label_margin - 4;
					}
				}
			}
			this.drawTicLines();
			
			return this.pieLabel.y + this.pieLabel.height; 
		}
		else
		{
			return minY;
		}
	}
	
	// Y value is from 0 to sc.Height from top to bottom
	public function moveLabelUp ( sc:ScreenCoordsBase, maxY: Float ): Float
	{
		if (this.pieLabel.visible)
		{
			var sign: Float = 1;
			var bAdjustToTop:Bool = false;
			var lblBottom: Float = this.getLabelBottomY();
			if (lblBottom > maxY)
			{
				// adjustment is negative here
				var adjust: Float = maxY - lblBottom;
				if ((maxY - this.pieLabel.height) < (sc.top + 1))
				{
					// calc adjust so label top is at top of screen
					adjust = sc.top - this.getLabelTopY();
					bAdjustToTop = true;
				}
				// Adjust the Y value
				this.pieLabel.y += adjust;

				if (!bAdjustToTop)
				{
					var lblRadius: Float = this.pieRadius + this.tick_size;
					var calcSin: Float = ((this.pieLabel.y + this.pieLabel.height / 2) - this.pieSlice.y) / lblRadius;
					calcSin = Math.max( -1, Math.min(1, calcSin));
					var Angle: Float = Math.asin(calcSin) / TO_RADIANS;

					if ((this.getTicAngle() > 90) && (this.getTicAngle() < 270))
					{
						Angle = 180 - Angle;
						sign = -1;
					}
					else if (this.getTicAngle() >= 270) 
					{
						Angle = 360 + Angle;
					}
					
					var X: Float = this.pieSlice.x + lblRadius * Math.cos(Angle * TO_RADIANS);
					if (this.isRightSide())
					{
						this.pieLabel.x = X + this.tick_extension_size + this.label_margin;
					}
					else
					{
						//if legend stands to the left side of the pie
						this.pieLabel.x = X - this.pieLabel.width -
									this.tick_extension_size - this.label_margin - 4;
					}
				}
			}
			this.drawTicLines();
			
			return this.pieLabel.y; 
		}
		else
		{
			return maxY;
		}
	}

	public function get_radius_offsets() : OFFSETSTRUCT {
		// Update the label text here in case pie slices change dynamically
		//var lblText:String = this.getText();
		//this.myPieLabel.setText(lblText);
		
		var offset:OFFSETSTRUCT = {
			top:animationOffset, right:animationOffset, 
			bottom:animationOffset, left:animationOffset
		};

		if (this.pieLabel.visible)
		{
			var ticAngle: Float = this.getTicAngle();
			var offset_threshold: Float = 20;
			var ticLength: Float = this.tick_size;
			
			if ((ticAngle >= 0) && (ticAngle <= 90)) 
			{
				offset.bottom = (ticAngle / 90) * ticLength + this.pieLabel.height / 2 + 1;
				offset.right = ((90 - ticAngle) / 90) * ticLength + this.tick_extension_size + this.label_margin + this.pieLabel.width;
			}
			else if ((ticAngle > 90) && (ticAngle <= 180)) 
			{
				offset.bottom = ((180 - ticAngle) / 90) * ticLength + this.pieLabel.height / 2 + 1;
				offset.left = ((ticAngle - 90) / 90) * ticLength + this.tick_extension_size + this.label_margin + this.pieLabel.width + 4;
			}
			else if ((ticAngle > 180) && (ticAngle < 270)) 
			{
				offset.top = ((ticAngle - 180) / 90) * ticLength + this.pieLabel.height / 2 + 1;
				offset.left = ((270 - ticAngle) / 90) * ticLength + this.tick_extension_size + this.label_margin + this.pieLabel.width + 4;
			}
			else // if ((ticAngle >= 270) && (ticAngle <= 360)) 
			{
				offset.top = ((360 - ticAngle) / 90) * ticLength + this.pieLabel.height / 2 + 1;
				offset.right = ((ticAngle - 270) / 90) * ticLength + this.tick_extension_size + this.label_margin + this.pieLabel.width;
			}
		}
		return offset;
	}
		function drawTicLines ():Void
	{
		if ((this.pieLabel.text != '') && (this.pieLabel.visible))
		{
			var ticAngle: Float = this.getTicAngle();
			
			var lblRadius: Float = this.pieRadius + this.tick_size;
			var lblAngle: Float = ticAngle * TO_RADIANS;

			var ticLblX: Float;
			var ticLblY: Float;
			if (this.pieSlice.isRightSide())
			{
				ticLblX = this.pieLabel.x - this.label_margin;
			}
			else
			{
				//if legend stands to the left side of the pie
				ticLblX = this.pieLabel.x + this.pieLabel.width + this.label_margin + 4;
			}
			ticLblY = this.pieLabel.y + this.pieLabel.height / 2;

			var ticArcX: Float = this.pieSlice.x + this.pieRadius * Math.cos(lblAngle);
			var ticArcY: Float = this.pieSlice.y + this.pieRadius * Math.sin(lblAngle);
			
			// Draw the line from the slice to the label
			this.graphics.clear();
			this.graphics.lineStyle( 1, this.pieSlice.get_color(), 1 );
			
			// move to the end of the tic closest to the label
			this.graphics.moveTo(ticLblX, ticLblY);
			// draw a line the length of the tic extender
			if (this.pieSlice.isRightSide())
			{
				this.graphics.lineTo(ticLblX - this.tick_extension_size, ticLblY);
			}
			else
			{
				this.graphics.lineTo(ticLblX + this.tick_extension_size, ticLblY);
			}
			// Draw a line from the end of the tic extender to the arc
			this.graphics.lineTo(ticArcX, ticArcY);
		}
	}

	public function getTicAngle (): Float
	{
		return this.pieSlice.getTicAngle();
	}

	public function isRightSide ():Bool
	{
		return this.pieSlice.isRightSide();
	}
}
